package com.apes.framework.plugin.condition.model;

import com.apes.framework.jpa.ApesBean;
import com.apes.framework.jpa.DefaultPersistListener;
import com.apes.framework.jpa.comment.Comment;
import com.apes.framework.jpa.generator.ApesGenerator;
import com.apes.framework.plugin.condition.PricingProcedureContext;
import com.apes.framework.plugin.soe.annotations.State;
import com.apes.framework.plugin.soe.annotations.States;
import com.apes.framework.plugin.soe.metadata.BusinessAttributeValue;
import com.apes.framework.util.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.SelectBeforeUpdate;
import org.springframework.util.StringUtils;

import javax.persistence.*;
import java.lang.reflect.Field;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * 功能：条件类型- 即条件分类，描述定价条件的属性，如价格、折扣、税额
 *
 * @author xul
 * @create 2018-11-06 17:11
 */
@Getter
@Setter
@SelectBeforeUpdate
@DynamicInsert
@DynamicUpdate
@Entity
@EntityListeners(DefaultPersistListener.class)
@Table(name = ConditionTypes.CONST_TABLE_NAME)
public class ConditionTypes implements ApesBean {
    public static final String CONST_TABLE_NAME = "tech_condition_types";

    @Id
    @Column(name = "id", length = 32)
    @ApesGenerator(code = CONST_TABLE_NAME, name = "条件类型")
    @Comment("编号")
    private String id;

    @Column(name = "name", nullable = false, length = 120)
    @Comment("描述")
    private String name;

//    @States(name = "定价等级", values = {
//            @State(id = "A", name = "折扣或附加费"),
//            @State(id = "B", name = "价格"),
//            @State(id = "D", name = "税")})
//    @ManyToOne(fetch = FetchType.LAZY)
//    @JoinColumn(name = "condition_class_id", nullable = false, referencedColumnName = "id")
//    private BusinessAttributeValue conditionClass;  //必填项，决定了标准化处理方式

    @States(name = "计算类型", values = {
            @State(id = "A", name = "百分数"),
            @State(id = "B", name = "固定金额"),
            @State(id = "C", name = "数量")})
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "calculation_type_id", nullable = false, referencedColumnName = "id")
    private BusinessAttributeValue calculationType; //必填项，确定系统如何计算定价中的价格、折扣或附加费，有数量（C）、固定金额（B）、百分数（A）等方式；

    @States(name = "舍入规则", values = {
            @State(id = "A", name = "四舍五入"),
            @State(id = "B", name = "向上取整"),
            @State(id = "C", name = "向下取整")})
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "rounding_rule_id", nullable = false, referencedColumnName = "id")
    private BusinessAttributeValue roundingRule; //有三个值可选，商业（A默认 四舍五入方式）、向上取整（B 无论多大总是入）、向下取整（C 无论多大总是舍）。

//    @JoinColumn(name = "loan", referencedColumnName = "id")
//    @Column(name = "loan", columnDefinition = "NUMBER(8) default 1")
//    @Comment("正/负项")
//    private int loan;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "access_sequences_id", referencedColumnName = "id")
    @Comment("存取顺序")
    private AccessSequences accessSequences;

    @States(name = "等级基值类型", values = {
            @State(id = "B", name = "价值"),
            @State(id = "C", name = "数量")})
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "scale_basis", nullable = false, referencedColumnName = "id")
    private BusinessAttributeValue scaleBasis; //等级基础定义了条件类型的等级是基于价值（B）或基于数量（C），还是基于重量（D）

    @States(name = "等级类型", values = {
            @State(id = "A", name = "代表从某个特定的数量或金额起对应的价格"),
            @State(id = "B", name = "代表从某个特定的数量或金额止对应的价格")})
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "scale_type", nullable = false, referencedColumnName = "id")
    private BusinessAttributeValue scaleType;

    @Column(name = "scale_formula", length = 250)
    @Comment("等级公式")
    private String scaleFormula;

    public boolean process(final PricingProcedureContext context) {
        context.setConditionTypes(this);
        return accessSequences.process(context);
    }

    /**
     * 计算定价值
     **/
    public boolean calcValue(PricingProcedureContext context, Object conditionRecord) {
        Double price = calcPrice(context, conditionRecord);
        if (price == null) return false;
        context.setPrice(price);
        context.setConditionRecord(conditionRecord);
        Double baseValue = calcBaseValue(context, conditionRecord);
        if (baseValue == null) return false;
        context.setBaseValue(baseValue);
        Double pricingValue = Arith.mul(price, baseValue);
        int scale = getScale(context);
        if (roundingRule.getCode().equals("B")) {
            pricingValue = Arith.roundUp(pricingValue, scale);
        } else if (roundingRule.getCode().equals("C")) {
            pricingValue = Arith.roundDown(pricingValue, scale);
        } else {
            pricingValue = Arith.round(pricingValue, scale);
        }
        context.setPricingValue(pricingValue);
        return true;
    }

    /**
     * 计算价格
     */
    private Double calcPrice(PricingProcedureContext context, Object conditionRecord) {
        Double price;
        Field field = ReflectUtil.getField(conditionRecord, "items");
        if (field != null) { /** 阶梯 **/
            Set items = ReflectUtil.getFieldValue(conditionRecord, "items");
            Double scaleBaseValue = calcScaleBaseValue(context, conditionRecord);
            price = getScalePrice(items, scaleBaseValue);
        } else {
            price = ReflectUtil.getFieldValue(conditionRecord, ConditionField.AMOUNT_FIELD_NAME);
        }
        if (price != null) {
            /** 计算类型：百分数 **/
            if (calculationType.getCode().equals("A")) price = price / 100;
            /** 条件定价单位 **/
            Double per = ReflectUtil.getFieldValue(conditionRecord, ConditionField.PER_FIELD_NAME);
            if (per != null) price = price / per;
            if (calculationType.getCode().equals("C")) { /** 数量 **/
                String topic = "event:cm.product.uom.computePrice";
                Object formUom = ReflectUtil.getFieldValue(conditionRecord, ConditionField.UOM_FIELD_NAME);
                Object toUom = context.getParameter(ConditionField.UOM_FIELD_NAME);
                Map body = MapUtil.mapper("formUom", formUom, "toUom", toUom, "price", price);
                price = (double) EventUtil.sendSynEvent(this, topic, body).stream().findFirst().get();
            }
        }
        return price;
    }

    /**
     * 计算基值
     **/
    private Double calcBaseValue(PricingProcedureContext context, Object conditionRecord) {
        String altCbv = context.getPricingProcedureItem().getAltCbv(); //条件基值的替代公式
        if (!StringUtils.isEmpty(altCbv)) {
            try {
                return (Double) CacheUtil.getEngine().execute(altCbv, MapUtil.mapper("ctx", context));
            } catch (Exception e) {
                throw new RuntimeException(String.format("表达式：[ %s ] 执行异常！ %s", altCbv, e.getMessage()));
            }
        }
        if (calculationType.getCode().equals("A")) { /** 百分数 **/
            throw new RuntimeException(String.format("基值类型：%s 未实现！", scaleBasis.getName()));
        } else if (calculationType.getCode().equals("B")) { /** 固定金额 **/
            throw new RuntimeException(String.format("基值类型：%s 未实现！", scaleBasis.getName()));
        } else if (calculationType.getCode().equals("C")) { /** 数量 **/
            return context.getParameterDouble(ConditionField.Qty_FIELD_NAME);
        }
        throw new RuntimeException(String.format("基值类型：%s 定义错误！", scaleBasis.getName()));
    }

    /**
     * 获取阶梯价
     */
    private Double getScalePrice(Set items, Double scaleBaseValue) {
        Optional optional;
        if (scaleType.getCode().equals("A")) {
            optional = items.stream().filter(item -> ((Double) ReflectUtil.getFieldValue(item, ConditionField.STEP_FIELD_NAME)) <= scaleBaseValue).max(ConditionTypes::compare);
        } else {
            optional = items.stream().filter(item -> ((Double) ReflectUtil.getFieldValue(item, ConditionField.STEP_FIELD_NAME)) >= scaleBaseValue).min(ConditionTypes::compare);
        }
        Object o = optional.orElse(null);
        if (o != null) {
            return ReflectUtil.getFieldValue(o, ConditionField.AMOUNT_FIELD_NAME);
        }
        return null;
    }

    /**
     * 计算等级基值
     **/
    private Double calcScaleBaseValue(PricingProcedureContext context, Object conditionRecord) {
        if (scaleBasis.getCode().equals("B")) { /** 价值 **/

        } else if (scaleBasis.getCode().equals("C")) { /** 数量 **/
            String topic = "event:cm.product.uom.computeQty";
            Object formUom = ReflectUtil.getFieldValue(conditionRecord, ConditionField.UOM_FIELD_NAME);
            Object toUom = context.getParameter(ConditionField.UOM_FIELD_NAME);
            Double inQty = context.getParameterDouble(ConditionField.Qty_FIELD_NAME);
            Map body = MapUtil.mapper("formUom", formUom, "toUom", toUom, "inQty", inQty);
            return (double) EventUtil.sendSynEvent(this, topic, body).stream().findFirst().get();
        }
        throw new RuntimeException(String.format("等级基值类型：%s 定义错误！", scaleBasis.getName()));
    }

    private int getScale(PricingProcedureContext context) {
        Object currency = context.getParameter(ConditionField.CURRENCY_FIELD_NAME);
        if (currency != null) {
            String topic = "event:cm.product.uom.computeQty";
            Map body = MapUtil.mapper("currency", currency);
            return (int) EventUtil.sendSynEvent(this, topic, body).stream().findFirst().get();
        }
        return ApesConst.DEF_SCALE;
    }

    public static int compare(Object o1, Object o2) {
        Double v1 = ReflectUtil.getFieldValue(o1, ConditionField.STEP_FIELD_NAME);
        Double v2 = ReflectUtil.getFieldValue(o2, ConditionField.STEP_FIELD_NAME);
        if (v1.doubleValue() > v2.doubleValue()) {
            return 1;
        }
        if (v1.doubleValue() == v2.doubleValue()) {
            return 0;
        }
        return -1;
    }
}
